package com.gitee.wsl.doc.prosemirror.model.node

import com.gitee.wsl.doc.prosemirror.model.Whitespace
import com.gitee.wsl.doc.prosemirror.model.attr.AttributeSpec
import com.gitee.wsl.doc.prosemirror.model.attr.AttributeSpecImpl
import com.gitee.wsl.doc.prosemirror.model.dom.out.DOMOutputSpec
import com.gitee.wsl.doc.prosemirror.model.dom.parser.ParseRuleMatch
import com.gitee.wsl.doc.prosemirror.model.dom.parser.rule.TagParseRule
import com.gitee.wsl.doc.prosemirror.model.dom.parser.rule.TagParseRuleImpl

// A description of a node type, used when defining a schema.
interface NodeSpec {
    // The content expression for this node, as described in the
    // [schema guide](/docs/guide/#schema.content_expressions). When not given, the node does not
    // allow any content.
    val content: String?

    // The marks that are allowed inside of this node. May be a space-separated string referring to
    // mark names or groups, `"_"` to explicitly allow all marks, or `""` to disallow marks. When
    // not given, nodes with inline content default to allowing all marks, other nodes default to
    // not allowing marks.
    val marks: String?

    // The group or space-separated groups to which this node belongs, which can be referred to in
    // the content expressions for the schema.
    val group: String?

    // Should be set to true for inline nodes. (Implied for text nodes.)
    val inline: Boolean

    // Can be set to true to indicate that, though this isn't a [leaf node](#model.NodeType.isLeaf),
    // it doesn't have directly editable content and should be treated as a single unit in the view.
    val atom: Boolean

    // The attributes that nodes of this type get.
    val attrs: Map<String, AttributeSpec>?

    // Controls whether nodes of this type can be selected as a
    // [node selection](#state.NodeSelection). Defaults to true for non-text nodes.
    val selectable: Boolean

    // Determines whether nodes of this type can be dragged without being selected.
    // Defaults to false.
    val draggable: Boolean

    // Can be used to indicate that this node contains code, which causes some commands to behave
    // differently.
    val code: Boolean

    // Controls way whitespace in this a node is parsed. The default is `"normal"`, which causes the
    // [DOM parser](#model.DOMParser) to collapse whitespace in normal mode, and normalize it
    // (replacing newlines and such with spaces) otherwise. `"pre"` causes the parser to preserve
    // spaces inside the node. When this option isn't given, but [`code`](#model.NodeSpec.code) is
    // true, `whitespace` will default to `"pre"`. Note that this option doesn't influence the way
    // the node is rendered—that should be handled by `toDOM` and/or styling.
    val whitespace: Whitespace?

    // Determines whether this node is considered an important parent node during replace operations
    // (such as paste). Non-defining (the default) nodes get dropped when their entire content is
    // replaced, whereas defining nodes persist and wrap the inserted content.
    val definingAsContext: Boolean?

    // In inserted content the defining parents of the content are preserved when possible.
    // Typically, non-default-paragraph textblock types, and possibly list items, are marked as
    // defining.
    val definingForContent: Boolean?

    // When enabled, enables both [`definingAsContext`](#model.NodeSpec.definingAsContext) and
    // [`definingForContent`](#model.NodeSpec.definingForContent).
    val defining: Boolean?

    // When enabled (default is false), the sides of nodes of this type count as boundaries that
    // regular editing operations, like backspacing or lifting, won't cross. An example of a node
    // that should probably have this enabled is a table cell.
    val isolating: Boolean?

    // Defines the default way a node of this type should be serialized to DOM/HTML (as used by
    // [`DOMSerializer.fromSchema`](#model.DOMSerializer^fromSchema)). Should return a DOM node or
    // an [array structure](#model.DOMOutputSpec) that describes one, with an optional number zero
    // (“hole”) in it to indicate where the node's content should be inserted.
    //
    // For text nodes, the default is to create a text DOM node. Though it is possible to create a
    // serializer where text is rendered differently, this is not supported inside the editor, so
    // you shouldn't override that in your text node spec.
    val toDOM: ((node: Node) -> DOMOutputSpec)?

    // Associates DOM parser information with this node, which can be used by
    // [`DOMParser.fromSchema`](#model.DOMParser^fromSchema) to automatically derive a parser. The
    // `node` field in the rules is implied (the name of this node will be filled in automatically).
    // If you supply your own parser, you do not need to also specify parsing rules in your schema.
    val parseDOM: List<TagParseRule>?

    // Defines the default way a node of this type should be serialized to a string representation
    // for debugging (e.g. in error messages).
    val toDebugString: ((node: Node) -> String)?

    // Defines the default way a [leaf node](#model.NodeType.isLeaf) of this type should be
    // serialized to a string (as used by [`Node.textBetween`](#model.Node^textBetween) and
    // [`Node.textContent`](#model.Node^textContent)).
    val leafText: ((node: Node) -> String)?

    // A single inline node in a schema can be set to be a linebreak
    // equivalent. When converting between block types that support the
    // node and block types that don't but have
    // [`whitespace`](#model.NodeSpec.whitespace) set to `"pre"`,
    // [`setBlockType`](#transform.Transform.setBlockType) will convert
    // between newline characters to or from linebreak nodes as
    // appropriate.
    val linebreakReplacement: Boolean?

    // Determines whether this node is automatically focused during navigation. Mainly used for navigation with arrow
    // key and backspace/delete key. Defaults to false.
    val autoFocusable: Boolean?

    // Node specs may include arbitrary properties that can be read by other code via
    // [`NodeType.spec`](#model.NodeType.spec).
    // [key: string]: any

    companion object
}

data class NodeSpecImpl(
    override val content: String? = null,
    override val marks: String? = null,
    override val group: String? = null,
    override val inline: Boolean = false,
    override val atom: Boolean = false,
    override val attrs: Map<String, AttributeSpec>? = null,
    override val selectable: Boolean = true,
    override val draggable: Boolean = false,
    override val code: Boolean = false,
    override val whitespace: Whitespace? = null,
    override val definingAsContext: Boolean? = null,
    override val definingForContent: Boolean? = null,
    override val defining: Boolean? = null,
    override val isolating: Boolean? = null,
    override val toDebugString: ((node: Node) -> String)? = null,
    override val leafText: ((node: Node) -> String)? = null,
    override val toDOM: ((node: Node) -> DOMOutputSpec)? = null,
    override val parseDOM: List<TagParseRule>? = null,
    override val autoFocusable: Boolean? = null,
    override val linebreakReplacement: Boolean? = null
) : NodeSpec

fun NodeSpec.Companion.setupBlock(tag: String): NodeSpec {
    return NodeSpecImpl(
        group = "block",
        content= "inline*",
    attrs = mapOf(
        "id"  to AttributeSpecImpl(default = null, validateString = "String|null"),
        "className" to AttributeSpecImpl(default = null, validateString = "String|null"),
        "styles" to AttributeSpecImpl(default = null, validateString = "String|null"),
    ),
    toDOM= { node->
        val src = node.attrs["id"]
        val alt = node.attrs["className"]
        val title = node.attrs["styles"]
        DOMOutputSpec.ArrayDOMOutputSpec(
            listOf(
                tag,
                mapOf(
                    "id" to src,
                    "className" to title,
                    "styles" to alt
                )
            )
        )
    },
    parseDOM= listOf(TagParseRuleImpl(tag = tag, getNodeAttrs = { dom ->
        ParseRuleMatch(
            mapOf(
                "id" to dom.attribute("id")?.value,
                "className" to dom.attribute("className")?.value,
                "styles" to dom.attribute("styles")?.value
            ),
            matches = true
        )
       })
      )
    )
}
